﻿using System;
using System.Collections;
using System.IO; 
using Parseq.Core; 
using Parseq.Util; 

namespace Parseq.Compiler
{
    class TokenReader : IReader, IDisposable
    {
		private CharReader reader; 
		private BaseParser parser; 
		private Hashtable skipList; // do I want this to be done here or passed in? 
        private Hashtable readTokens = new Hashtable(); 
		public TokenReader(BaseParser parser, Hashtable skipList, CharReader reader)  { 
			this.parser = parser; 
			this.skipList = skipList; 
			this.reader = reader; 
		}

        public TokenReader(BaseParser parser, Hashtable skipList, Stream s)
        {
			this.parser = parser; 
			this.skipList = skipList; 
			this.reader = new CharReader(s); 
		}
		
		public object Peek() { // what does peek do here? not too much. but what do I want to 
			Location temp = this.reader.Location; 
			Token result = (Token)this.Read(); 
			this.reader.Location = temp; 
			return result; 
		}
		
		// how to read the ones that are being skipped? they'll be appended to the next successful read. 
		public object Read() { 
			// at the very beginning there is a single point where we have a 
			Token temp; 
			do {
				temp = this.readToken(); 
			} while (this.skipList.Contains(temp.Type)); 
			return temp; 
		}
		
		private Token readToken() {
            Location current = this.Location; 
			if (this.Location.Bytes == -1) {
				this.Location = this.Location.IncBytes(1); 
				return new Token("BOF", null); 
			}
			
			int nextChar = (int)this.reader.Peek(); 
			if (nextChar == -1) { // we have more bytes to read.
				return new Token("EOF", null); 
			}
            if (this.readTokens.Contains(current.Bytes)) // this is memoization. not sure if this is worth it rather than parsing.. hmm... 
            {
                // in order to cache we'll also need to know what the next Location will be so we can advance appropriately! 
                object[] result = (object[])this.readTokens[this.Location.Bytes];
                this.Location = new Location((long)result[1]);
                return (Token)result[0];
            }
            else
            {
                Result result = this.parser.Parse(this.reader, this.Location);
			    if (Result.Succeeded(result)) {
                    this.readTokens[current.Bytes] = new object[2]{ result.Inner , result.Position.Bytes }; 
				    if (result.Inner is Token) {
					    return (Token)result.Inner; 
				    } else {
					    return new Token("UNKNOWN", result.Inner); 
				    }
			    } else {
				    return new Token("FAIL", null); // should this just return NULL? not sure at this moment... hmmm... 
			    }
            }
        }
		
		public Location Location {
			get { return this.reader.Location; } 
			set { this.reader.Location = value; } 
		}
		
		public void Dispose() {
			this.reader.Dispose(); 
		}
    }
}
